#ifndef __M_ROOM_H__
#define __M_ROOM_H__

#include "logger.hpp"
#include "db.hpp"
#include "online.hpp"
#include "util.hpp"

#define BOARD_ROW 15
#define BOARD_COL 15
#define CHESS_WHITE 1
#define CHESS_BLACK 2

typedef enum
{
    GAME_START,
    GAME_OVER
} room_state;

class room
{
private:
    int _room_id;       // 房间标识id
    room_state _state;  // 房间状态
    int _player_number; // 房间中玩家数量
    int _white_id;
    int _black_id;
    user_table *_ut;                      // 数据管理模块句柄(里面的操作都是线程安全的)
    online_manager *_om;                  // 在线用户管理模块句柄(里面的操作都是线程安全的)
    std::vector<std::vector<int>> _board; // 棋盘信息
    std::mutex _mutex;                    // 保护共享资源_player_number
private:
    bool check_win(int row, int col, int chess_color)
    {
        // 1.从下棋位置的四个方向(横 竖 正斜 反斜)上检测是否出现了5个或以上与chess_color颜色相同的棋子
        return check(row, col, 0, 1, chess_color) || check(row, col, 1, 0, chess_color) ||
               check(row, col, -1, -1, chess_color) || check(row, col, -1, 1, chess_color);
    }
    bool check(int row, int col, int offset_row, int offset_col, int color)
    {
        // 1.参数解释: offset_row代表从当前位置行偏移±offset_row，offset_col代表从当前位置偏移±offset_col
        int ret = 1;
        int cur_row = row + offset_row, cur_col = col + offset_col;
        {
            std::unique_lock<std::mutex> lock(_mutex);
            while (cur_row < BOARD_ROW && cur_col < BOARD_COL && cur_row >= 0 && cur_col >= 0 && _board[cur_row][cur_col] == color)
            {
                ret++;
                cur_row += offset_row, cur_col += offset_col;
            }
        }
        cur_row = row - offset_row, cur_col = col - offset_col;
        {
            std::unique_lock<std::mutex> lock(_mutex);
            while (cur_row < BOARD_ROW && cur_col < BOARD_COL && cur_row >= 0 && cur_col >= 0 && _board[cur_row][cur_col] == color)
            {
                ret++;
                cur_row -= offset_row, cur_col -= offset_col;
            }
        }
        return ret >= 5;
    }

public:
    room(int room_id, user_table *ut, online_manager *om)
        : _room_id(room_id), _state(GAME_START), _player_number(0), _ut(ut), _om(om), _board(BOARD_ROW, std::vector<int>(BOARD_COL))
    {
        DLOG("%d号游戏房间创建成功", _room_id);
    }
    ~room()
    {
        DLOG("%d号游戏房间销毁成功", _room_id);
    }
    int get_room_id() { return _room_id; }
    room_state get_room_state() { return _state; }
    int get_room_playerNum() { return _player_number; }
    void add_white_user(int uid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _white_id = uid;
        _player_number++;
    }
    void add_black_user(int uid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _black_id = uid;
        _player_number++;
    }
    int get_white_user() { return _white_id; }
    int get_black_user() { return _black_id; }
    void handle_request(Json::Value &req) // 根据不同的客户端请求(下棋，退出，聊天)，去下面调用不同的处理函数，得到响应后进行广播
    {
        // 1.对请求中room_id字段是否与本房间号相同(统一校验，下面的接口就不需要校验了)
        Json::Value resp;
        if (req["room_id"].asInt() != _room_id)
        {
            resp["optype"] = req["optype"].asString();
            resp["result"] = false;
            resp["reason"] = "房间号不匹配";
            broadcast(resp);
            return;
        }
        // 2.针对不同类型的请求，调用不同的处理函数
        if (req["optype"].asString() == "put_chess")
        {
            resp = handle_chess(req);
            if (resp["winner"].asInt() != 0)
            {
                // 2.1 如果分出胜负，则在这个地方统一更新数据库信息
                int winner_id = resp["winner"].asInt();
                int loser_id = (winner_id == _white_id ? _black_id : _white_id);
                _ut->win(winner_id);
                _ut->lose(loser_id);
                std::unique_lock<std::mutex> lock(_mutex);
                _state = GAME_OVER;//可能产生线程安全问题的共享资源
            }
        }
        else if (req["optype"].asString() == "chat")
        {
            resp = handle_chat(req);
        }
        else
        {
            resp["optype"] = req["optype"].asString();
            resp["result"] = false;
            resp["reason"] = "未知请求类型!";
        }
        broadcast(resp); // 广播发送给当前房间中的所有用户
        return;
    }
    Json::Value handle_chess(Json::Value &req) // 来自客户端的下棋请求，响应要广播给房间中的所有用户()
    {
        Json::Value resp = req;
        // 2.判断房间中的两个用户是否都在线，任意一个不在线，则与之相对的用户胜利
        int cur_uid = req["uid"].asInt(), cur_row = req["row"].asInt(), cur_col = req["col"].asInt();
        bool ret1 = _om->is_in_game_room(_white_id);
        if (ret1 == false)
        {
            resp["result"] = true;
            resp["reason"] = "运气真好, 对方掉线, 不战而胜!";
            resp["winner"] = _black_id;
            return resp;
        }
        bool ret2 = _om->is_in_game_room(_black_id);
        if (ret2 == false)
        {
            resp["result"] = true;
            resp["reason"] = "运气真好, 对方掉线, 不战而胜!";
            resp["winner"] = _white_id;
            return resp;
        }
        // 3.判断下棋的位置是否合理(客户端下棋位置是否已经被占用)
        int cur_color = (cur_uid == _white_id ? CHESS_WHITE : CHESS_BLACK);
        {
            std::unique_lock<std::mutex> lock(_mutex);
            if (_board[cur_row][cur_col] != 0)
            {
                resp["result"] = false;
                resp["reason"] = "您所下的位置已有棋子!";
                return resp;
            }
            _board[cur_row][cur_col] = cur_color; // 走棋
        }
        // 4.判断是否有玩家胜利(未分胜负，已分胜负)
        if (check_win(cur_row, cur_col, cur_color))
        {
            resp["result"] = true;
            resp["reason"] = "恭喜您已获得胜利";
            resp["winner"] = cur_uid;
            return resp;
        }
        else
        {
            resp["result"] = true;
            resp["reason"] = "未分胜负!";
            resp["winner"] = 0; // winner为0，代表没有人胜利
            return resp;
        }
    }
    Json::Value handle_chat(Json::Value &req) // 来自客户端的聊天请求，响应要广播给房间中的所有用户()
    {
        Json::Value resp = req;
        // 2.检测消息中是否有敏感词(我们这里只是做一个用例，就不封装接口了，封装接口可以增加扩展性，比如增加敏感词都有哪些)
        std::string msg = req["message"].asString();
        size_t pos = msg.find("垃圾");
        if (pos != std::string::npos)
        {
            resp["result"] = false;
            resp["reason"] = "消息中包含了敏感词! 不能发送!";
            return resp;
        }
        // 3.广播消息给房间中的所有用户
        resp["result"] = true;
        return resp;
    }
    // 退出房间的接口并不是处理请求到来时调用的接口，而是当用户连接断开时才会去调用的接口(这个接口和处理请求的接口逻辑是不同的，返回值是void)
    void handle_exit(int uid)
    {
        Json::Value resp;
        // 1.如果是下棋中退出，则对方胜利，如果是下棋结束退出，则是正常退出
        std::unique_lock<std::mutex> lock(_mutex);
        if (_state == GAME_START)
        {
            resp["optype"] = "put_chess";
            resp["result"] = true;
            resp["reason"] = "运气真好, 对方掉线, 不战而胜!";
            resp["room_id"] = _room_id;
            resp["uid"] = uid; // 当前退出操作是由谁发起的
            resp["row"] = -1;
            resp["col"] = -1;
            resp["winner"] = (uid == _black_id ? _white_id : _black_id);
            int win_id = (uid == _black_id ? _white_id : _black_id);
            int lose_id = uid;
            _ut->lose(lose_id);
            _ut->win(win_id);
            _state = GAME_OVER;
            broadcast(resp); // 如果下棋中退出，则已分胜负，需要广播消息
        }
        // 2.房间中玩家数量--
        _player_number--;
        return;
    }
    void broadcast(Json::Value &resp) // 将信息广播给房间中所有的玩家
    {
        // 1.对要响应的信息进行序列化，将Json::Value中的数据序列化成为json格式字符串
        std::string body;
        json_util::serialize(resp, body);
        // 2.获取房间中所有用户的通信连接并广播信息
        wsserver_t::connection_ptr bconn = _om->get_conn_from_room(_black_id);
        if (bconn.get() != nullptr)
            bconn->send(body);
        wsserver_t::connection_ptr wconn = _om->get_conn_from_room(_white_id);
        if (wconn.get() != nullptr)
            wconn->send(body);
        return;
    }
};

using room_ptr = std::shared_ptr<room>;

class room_manager
{
private:
    int _next_rid; // 房间id分配器
    user_table *_ut;
    online_manager *_om;
    std::mutex _mutex;
    std::unordered_map<int, room_ptr> _rooms;
    std::unordered_map<int, int> _ur_ids;
public:
    room_manager(user_table *ut, online_manager *om)
        : _ut(ut), _om(om), _next_rid(1)
    {
        DLOG("房间管理模块初始化完毕!");
    }
    ~room_manager() { DLOG("房间管理模块即将销毁!"); }
    room_ptr create_room(int uid1, int uid2) // 为两个用户创建房间，并返回房间管理句柄
    {
        // 1.两个用户在游戏大厅中进行对战匹配，匹配成功后创建房间，在进入游戏房间后，会与服务器重新建立websocket长连接
        //   两个长连接各有各的功能，互不干扰，游戏大厅的长连接主要负责对战匹配，游戏房间的长连接主要负责下棋聊天等业务的实现
        // 2.所以首先需要做的就是甄别这两个用户是否都在游戏大厅中，同时在游戏大厅里面，我们才会为其创建一个房间
        if (!_om->is_in_game_hall(uid1))
        {
            DLOG("用户%d不在游戏大厅中, 创建房间失败!", uid1);
            return room_ptr();
        }
        if (!_om->is_in_game_hall(uid2))
        {
            DLOG("用户%d不在游戏大厅中, 创建房间失败!", uid2);
            return room_ptr();
        }

        // 3.创建房间后，将用户信息添加到房间中
        std::unique_lock<std::mutex> lock(_mutex);  // 下面对房间添加用户，哈希表添加映射关系等操作全部都加锁保护
        room_ptr rp(new room(_next_rid, _ut, _om)); // 这个智能指针对象是在栈上开辟的，出了这个函数就会被销毁
        rp->add_black_user(uid1);
        rp->add_white_user(uid2);
        // 4.将创建好的房间通过哈希表管理起来
        _rooms.insert(std::make_pair(_next_rid, rp)); // 哈希表在堆上的，将智能指针对象存储到哈希表，则相当于堆上有了一份智能指针
        _ur_ids.insert(std::make_pair(uid1, _next_rid));
        _ur_ids.insert(std::make_pair(uid2, _next_rid));
        _next_rid++;
        // 5.最后返回房间管理句柄即可
        return rp;
    }
    room_ptr get_room_by_rid(int rid) // 通过房间id获取房间信息
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _rooms.find(rid);
        if (it == _rooms.end())
            return room_ptr();
        return it->second;
    }
    room_ptr get_room_by_uid(int uid) // 通过用户id获取房间信息
    {
        std::unique_lock<std::mutex> lock(_mutex);
        // 1.通过用户id获取房间id
        auto it1 = _ur_ids.find(uid);
        if (it1 == _ur_ids.end())
            return room_ptr();
        int rid = it1->second;
        // 2.通过房间id获取房间信息
        auto it2 = _rooms.find(rid);
        if (it2 == _rooms.end())
            return room_ptr();
        return it2->second;
    }
    void destroy_room(int rid) // 通过房间id销毁房间
    {
        // 1.因为房间是通过room_ptr来管理的，而room_ptr会被保存到_rooms哈希表中，因此销毁房间其实就是销毁哈希表中含有room_ptr的键值对即可
        // 2.但销毁房间不仅仅销毁房间就可以了，还需要将哈希表中存储的uid和rid的映射关系也要移除
        room_ptr rp = get_room_by_rid(rid);
        if (rp.get() == nullptr)
            return; // 没有房间信息，移除个毛啊
        int uid1 = rp->get_black_user();
        int uid2 = rp->get_white_user();

        std::unique_lock<std::mutex> lock(_mutex);
        _ur_ids.erase(uid1);
        _ur_ids.erase(uid2);
        _rooms.erase(rid);
    }
    void remove_room_user(int uid) // 删除房间中的指定用户，如果房间中无用户，则销毁房间(当用户断开连接时，会调用这个接口)
    {
        room_ptr rp = get_room_by_uid(uid);
        if (rp.get() == nullptr)
            return;
        // 房间中的某个玩家要退出
        rp->handle_exit(uid);
        // 房间中没有玩家了，则销毁房间
        if (rp->get_room_playerNum() == 0)
            destroy_room(rp->get_room_id());
    }
};

#endif